﻿using System;
using Microsoft.Win32;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;



namespace InnovTech.Util.ComEx
{
    /// <summary>
    /// A helper class which wraps the native methods for COM and gives some useful methods to register an "out-of-process" EXE-COM-Assembly.
    /// </summary>
    public static class DotNetComEx
    {

        #region Register Assembly

        public static void RegisterAssembly(string exeCOMFilePath)
        {
            RegisterAssembly(Assembly.LoadFrom(exeCOMFilePath));
        }


        public static void RegisterAssembly(Assembly assembly)
        {
            if (assembly == null)
                throw new ArgumentException("The given assembly to register as COM can not be null");
            RegistrationServices rs = new RegistrationServices();
            if (rs.RegisterAssembly(assembly, AssemblyRegistrationFlags.SetCodeBase))
            {
                throw new ArgumentException("The given assembly can not be registered as COM Assembly");
            }

        }

        public static void UnregisterAssembly(Assembly assembly)
        {
            RegistrationServices rs = new RegistrationServices();
            rs.UnregisterAssembly(assembly);
        }

        public static void UnregisterAssembly(string exeCOMFilePath)
        {
            UnregisterAssembly(Assembly.LoadFile(exeCOMFilePath));
        }

        #endregion Register Assembly


        #region Register Types

        public static int[] RegisterComTypes(Assembly assembly, IEnumerable<Type> classesForClient, RegistrationConnectionType connType = RegistrationConnectionType.MultipleUse)
        {
            if (assembly == null)
                throw new ArgumentException("The given assembly to register as COM can not be null");

            List<int> cookies = new List<int>();

            if (classesForClient != null && classesForClient.Any())
            {
                RegistrationServices rs = new RegistrationServices();

                foreach (Type t in classesForClient)
                {
                    int cookie = rs.RegisterTypeForComClients(t, RegistrationClassContext.LocalServer, connType);
                    cookies.Add(cookie);
                }
            }
            return cookies.ToArray();
        }

        public static int[] RegisterComTypes(string exeCOMFilePath, IEnumerable<Type> classesForClient, RegistrationConnectionType connType = RegistrationConnectionType.MultipleUse)
        {
            return RegisterComTypes(Assembly.LoadFrom(exeCOMFilePath), classesForClient, connType);
        }




        public static void UnregisterComTypes(IEnumerable<int> classesForClient)
        {

            if (classesForClient != null && classesForClient.Any())
            {
                RegistrationServices rs = new RegistrationServices();
                foreach (int cookie in classesForClient)
                {
                    rs.UnregisterTypeForComClients(cookie);
                }
            }
        }

        #endregion Register Types


        #region Register type as Local Server

        /// <summary>
        /// Register the component as a local server. This method should be only used for the "out-of-process" Exe-Assembly.
        /// </summary>
        /// <param name="t"></param>
        public static void SwitchComToLocalServer(Type t)
        {

            // Open the CLSID key of the component.
            using (RegistryKey keyCLSID = Registry.ClassesRoot.OpenSubKey(
                @"CLSID\" + t.GUID.ToString("B"), /*writable*/true))
            {

                // Remove the auto-generated InprocServer32 key after registration
                // (REGASM puts it there but we are going out-of-proc).
                keyCLSID.DeleteSubKeyTree("InprocServer32");

                // Create "LocalServer32" under the CLSID key
                using (RegistryKey subkey = keyCLSID.CreateSubKey("LocalServer32"))
                {
                    subkey.SetValue("", t.Assembly.Location,
                        RegistryValueKind.String);

                }
            }
        }

        /// <summary>
        /// Unregister the component.
        /// </summary>
        /// <param name="t"></param>
        public static void UnregisterComponent(Type t)
        {
            // Delete the CLSID key of the component
            Registry.ClassesRoot.DeleteSubKeyTree(@"CLSID\" + t.GUID.ToString("B"));
        }

        #endregion Register type as Local Server


        #region Type Lib


        public static void CreateTypeLibrary(Assembly assembly, string tlbPath, TypeLibExporterFlags flag)
        {
            CreateTypeLibrary(assembly, tlbPath, flag, new EmptyTypeLibExporterNotifySink());
        }

        public static void CreateTypeLibrary(Assembly assembly, string tlbPath, TypeLibExporterFlags flag, ITypeLibExporterNotifySink notifySink)
        {
            TypeLibConverter converter = new TypeLibConverter();
            UCOMICreateITypeLib typeLib = converter.ConvertAssemblyToTypeLib(assembly, tlbPath, flag, notifySink) as UCOMICreateITypeLib;
            if (typeLib == null)
                throw new Exception("Convert Assembly to Type library is failed");
            typeLib.SaveAllChanges();
        }

        public static void RegisterTypeLibrary(string tlbPath)
        {
            IntPtr obj = IntPtr.Zero;
            COMNative.LoadTypeLib(tlbPath, ref obj);
            COMNative.RegisterTypeLib(obj, tlbPath, "");
        }

        public static void UnregisterTypeLibrary(string tlbPath)
        {
            IntPtr obj = IntPtr.Zero;
            COMNative.LoadTypeLib(tlbPath, ref obj);
            System.Runtime.InteropServices.ComTypes.TYPELIBATTR tlbAttr = (System.Runtime.InteropServices.ComTypes.TYPELIBATTR)Marshal.PtrToStructure(obj, typeof(System.Runtime.InteropServices.ComTypes.TYPELIBATTR));
            Guid guid = new Guid();
            object rsult = COMNative.UnRegisterTypeLib(ref guid, tlbAttr.wMajorVerNum, tlbAttr.wMinorVerNum, tlbAttr.lcid, tlbAttr.syskind);

            Debug.WriteLine(rsult.ToString());
        }



        #endregion Type Lib
    }

    internal class EmptyTypeLibExporterNotifySink : ITypeLibExporterNotifySink
    {
        public void ReportEvent(ExporterEventKind eventKind, int eventCode, string eventMsg)
        {
        }

        public object ResolveRef(Assembly assembly)
        {
            return null;
        }
    }
}



